package domain.searchEngine.origin;

import java.util.Calendar ;
import java.util.Date ;
import java.util.HashSet ;
import java.util.Iterator ;
import java.util.Set ;
import java.util.Vector ;

import persistence.OriginIndex ;
import presentation.UIPost ;
import util.Bug ;
import util.DateTools ;
import util.NotFoundException ;
import util.Statistics ;
import domain.ForumData ;
import domain.message.Post ;
import domain.searchEngine.SearchEngine ;
import domain.searchEngine.SearchResult ;
import domain.user.Login ;


/**
 * The Class SearchEngine functions like server between the domain layer and the persistence layer
 * responsible for finding all desirable data that the client wants.
 */
public class OriginSearchEngine implements SearchEngine
{
	/** Index class which contains all data structure. */
	private OriginIndex index;
	private ForumData forum ;

	/**
	 * Instantiates a new search engine.
	 *
	 * @param index the index which contains all data structure.
	 * @param forum where to obtain domain objects from.
	 */
	public OriginSearchEngine(final OriginIndex index, final ForumData forum)
	{
		this.index = index;
		this.forum = forum ;
	}

	/**
	 * @see domain.searchEngine.SearchEngine#close()
	 */
	@Override
	public void close()
	{
		this.index.saveIndex() ;
	}

	/**
	 * @see domain.searchEngine.SearchEngine#getPostStatistics()
	 */
	public Statistics getPostStatistics()
	{
		try
		{
			Date now = new Date() ;
			Date yesterday = DateTools.add(now, Calendar.DATE, -1) ;
			Set<Long> posts = this.index.getMessagesByDate(yesterday, now) ;
			Statistics result = new Statistics(24) ;
			for (Long postId: posts)
			{
				Post post = this.forum.getMessages().getPost(postId) ;
				int hour = DateTools.get(post.getDate(), Calendar.HOUR_OF_DAY) ;
				result.increment(hour) ;
			}
			return result ;
		}
		catch (NotFoundException e)
		{
			e.printStackTrace() ;
			throw new Bug("index is inconsistent: " + e.getSearchKey(), e) ;
		}
	}

	/**
	 * @see domain.searchEngine.SearchEngine#getPostStatisticsForMember(long)
	 */
	public Statistics getPostStatisticsForMember(final long memberId)
	{
		try
		{
			Date now = new Date() ;
			Date lastMonth = DateTools.add(now, Calendar.MONTH, -1) ;
			Set<Long> posts = this.index.getMessagesByAuthorId(memberId, lastMonth, now) ;
			Statistics result = new Statistics(32) ;
			for (Long postId: posts)
			{
				Post post = this.forum.getMessages().getPost(postId) ;
				int day = DateTools.get(post.getDate(), Calendar.DATE) ;
				result.increment(day) ;
			}
			return result ;
		}
		catch (NotFoundException e)
		{
			e.printStackTrace() ;
			throw new Bug("index is inconsistent", e) ;
		}
	}

	/**
	 * @see domain.searchEngine.SearchEngine#getUserStatistics()
	 */
	public Statistics getUserStatistics()
	{
		try
		{
			Date now = new Date() ;
			Date yesterday = DateTools.add(now, Calendar.DATE, -1) ;
			Set<Long> logins = this.index.getLoginsByDate(yesterday, now) ;
			Statistics result = new Statistics(24) ;
			for (Long loginId: logins)
			{
				Login login = this.forum.getStatistics().getLogin(loginId) ;
				int hour = DateTools.get(login.getTime(), Calendar.HOUR_OF_DAY) ;
				result.increment(hour) ;
			}
			return result ;
		}
		catch (NotFoundException e)
		{
			e.printStackTrace() ;
			throw new Bug("index is inconsistent", e) ;
		}
	}

	/**
	 * Search by author. searches for post id according to author username that the user typed.
	 * responsible for uploading all posts id
	 *
	 * @param authorName the username of the authors of the messages that the user wants
	 *
	 * @return the set< long> all desirable messages id
	 *

	 */
	public Set<SearchResult> searchByAuthor(final String authorName)
	{
		Long authorId = this.index.getAuthorByUserName(authorName);
		if (authorId == null)
			return new HashSet<SearchResult>() ;
		Set<Long> currentMessages =  this.index.getMessagesByAuthorId(authorId);
		return this.createSearchResultSet(this.retrieve(currentMessages));
	}

	/**
	 * Search by content. searches for posts id according to content that the user wrote
	 * responsible for uploading all Posts id that contain the same words that the client asks
	 *
	 * @param message the message  that the user want to get messages according to
	 *
	 * @return set< long> all desirable messages

	 */
	public Set<SearchResult> searchByContent(final String message)
	{
		/*two cases :
		 * 1.the user want exact phase
		 * 2. the user want all the sentence that contain the words X */
		//case of looking for exact phase
		if(message.startsWith("\"") & (message.endsWith("\"")))
		{
			//split the sentence for the inverted commas
			final String charSequenceOfMessage = message.substring(1, message.length()-1);
			//retrieve all messages that contain all the words from the sentence
			final Set<UIPost> allMessages =  this.retrieve(this.searchContent(message));
			//the idea is to filter all messages that contains the exact phase in their string message
			//create a new set of message that will contain all the messages that contain the exact phase the user ask
			final Set<UIPost> filterMessages = new HashSet<UIPost>();
			//Iterator for the set
			final Iterator<UIPost> messageIterator = allMessages.iterator();

			while(messageIterator.hasNext())
			{
				//retrieve the message and checks if the messages contain the phase
				final UIPost currentMessage = messageIterator.next();
				//if contain insert to result set
				if(currentMessage.getMessage().contains(charSequenceOfMessage))
				{
					filterMessages.add(currentMessage);
				}
			}
			return this.createSearchResultSet(filterMessages);
		}
		//case of looking for separate words
		return this.createSearchResultSet(this.retrieve(this.searchContent(message)));
	}

	/**
	 * Search by date. searches for post according to range of dates
	 *
	 * @param fromDate the date from which the user wants the messages
	 * @param toDate the date to which the user wants the messages
	 *
	 * @return the set< long> all desirable messages id
	 *

	 */
	public Set<SearchResult> searchByDate(final Date fromDate,final Date toDate)
	{
		Date currentDate = fromDate;
		Set<Long> currentMessages = new HashSet<Long>();
		while (currentDate.before(toDate))
		{
			currentMessages.addAll(this.index.getMessagesByDate(currentDate));
			long dayTime = (60000*60*24)+currentDate.getTime();
			currentDate = new Date(dayTime);
		}
		return this.createSearchResultSet(this.retrieve(currentMessages));
	}

	private Set<SearchResult> createSearchResultSet(final Set<UIPost> posts){
		Set<SearchResult> ans = new HashSet<SearchResult>();
		for (final UIPost post: posts)
		{
			ans.add(new SearchResult(post, 0));
		}
		return ans;
	}

	/**
	 * Retrieve all messages id to the UIPost Class
	 *
	 * @param messagesIds set of the messages ids which we wants to retrieve
	 *
	 * @return set< post>  all desirable messages
	 */
	private Set<UIPost> retrieve(final Set<Long> messagesIds)
	{
		Set<Post> posts = null;

		try
		{
			posts = this.forum.getMessages().getAllPosts(messagesIds) ;
		}
		catch (NotFoundException e)
		{
			System.out.println("cant find messages") ;
		}
		final Set<UIPost> uiPosts = new HashSet<UIPost>(posts);
		return uiPosts;

	}

	private Set<Long> searchContent(final String message)
	{
		Set<Long> finalMessages = new HashSet<Long>();
		Set<Long> temporaryMessages = new HashSet<Long>();
		Vector<String> separatedWords = this.separate(message);

		Long wordId = this.index.getWordsByName(separatedWords.get(0));
		if (wordId == null)
		{
			return new HashSet<Long>() ;
		}
		finalMessages = this.index.getWordInMessagesByWordId(wordId);

		for(int i=1;i<separatedWords.size();i++)
		{
			wordId = this.index.getWordsByName(separatedWords.get(i));
			temporaryMessages = this.index.getWordInMessagesByWordId(wordId);
			finalMessages.retainAll(temporaryMessages);
			temporaryMessages.clear();
		}

		return finalMessages;
	}

	/**
	 * Separate. function is responsible for splitting the message into words
	 *
	 * @param message the message that we want to split
	 *
	 * @return the vector< string> which contain all the words from the sentence
	 */
	private Vector<String> separate(final String message)
	{
		Vector<String> separatedWords = new Vector<String>();
		String[] arrayOfWords = message.split(" ");
		for (String arrayOfWord: arrayOfWords)
		{
			separatedWords.add(arrayOfWord);
		}
		return separatedWords;
	}

}
